"""
文件处理工具模块
提供文件上传、保存、类型检查等功能
"""

import os
import hashlib
import re
from pathlib import Path
from datetime import datetime
from typing import Optional, BinaryIO, List, Dict
from io import BytesIO
import logging

logger = logging.getLogger(__name__)


def get_file_extension(filename: str) -> str:
    """
    获取文件扩展名（小写）
    
    Args:
        filename: 文件名
        
    Returns:
        文件扩展名（不含点号）
    """
    return Path(filename).suffix.lower().lstrip('.')


def validate_file_extension(filename: str, allowed_extensions: list) -> bool:
    """
    验证文件扩展名是否允许
    
    Args:
        filename: 文件名
        allowed_extensions: 允许的扩展名列表
        
    Returns:
        是否允许
    """
    ext = get_file_extension(filename)
    return ext in [e.lower() for e in allowed_extensions]


def get_file_hash(file_content: bytes) -> str:
    """
    计算文件内容的MD5哈希值
    
    Args:
        file_content: 文件内容（字节）
        
    Returns:
        MD5哈希值
    """
    return hashlib.md5(file_content).hexdigest()


def save_uploaded_file(
    file_content: bytes,
    original_filename: str,
    upload_dir: str = "uploads"
) -> str:
    """
    保存上传的文件
    
    Args:
        file_content: 文件内容（字节）
        original_filename: 原始文件名
        upload_dir: 上传目录
        
    Returns:
        保存后的文件路径
    """
    try:
        # 创建上传目录
        upload_path = Path(upload_dir)
        upload_path.mkdir(parents=True, exist_ok=True)
        
        # 生成唯一文件名：时间戳_哈希值_原始文件名
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        file_hash = get_file_hash(file_content)[:8]
        ext = get_file_extension(original_filename)
        
        # 保留原始文件名的主要部分（去除扩展名）
        base_name = Path(original_filename).stem
        # 清理文件名，移除特殊字符
        base_name = "".join(c for c in base_name if c.isalnum() or c in ('-', '_', '.'))[:50]
        
        new_filename = f"{timestamp}_{file_hash}_{base_name}.{ext}"
        file_path = upload_path / new_filename
        
        # 保存文件
        with open(file_path, 'wb') as f:
            f.write(file_content)
        
        logger.info(f"文件保存成功: {file_path}")
        return str(file_path)
        
    except Exception as e:
        logger.error(f"文件保存失败: {e}")
        raise


def get_file_size_mb(file_content: bytes) -> float:
    """
    获取文件大小（MB）
    
    Args:
        file_content: 文件内容（字节）
        
    Returns:
        文件大小（MB）
    """
    return len(file_content) / (1024 * 1024)


def clean_html_tags(text: str, for_markdown_table: bool = True) -> str:
    """
    清理文本中的HTML标签
    
    Args:
        text: 包含HTML标签的文本
        for_markdown_table: 是否用于Markdown表格（True时<br>转为分号，False时转为换行符）
        
    Returns:
        清理后的纯文本
    """
    if not text:
        return text
    
    # 将<br>标签转换为分号或换行符
    if for_markdown_table:
        # Markdown表格中不能有真实的换行符，用分号代替
        cleaned = re.sub(r'<br\s*/?>|<BR\s*/?>', '；', text, flags=re.IGNORECASE)
    else:
        # 其他场景转换为换行符
        cleaned = re.sub(r'<br\s*/?>|<BR\s*/?>', '\n', text, flags=re.IGNORECASE)
    
    # 移除其他常见HTML标签
    cleaned = re.sub(r'</?p>|</?div>|</?span>|</?strong>|</?b>|</?i>|</?em>', '', cleaned, flags=re.IGNORECASE)
    
    # 处理HTML实体
    cleaned = re.sub(r'&nbsp;', ' ', cleaned)
    cleaned = re.sub(r'&lt;', '<', cleaned)
    cleaned = re.sub(r'&gt;', '>', cleaned)
    cleaned = re.sub(r'&amp;', '&', cleaned)
    cleaned = re.sub(r'&quot;', '"', cleaned)
    
    return cleaned


def cleanup_old_files(directory: str, days: int = 7):
    """
    清理指定天数前的旧文件
    
    Args:
        directory: 目录路径
        days: 保留天数
    """
    try:
        dir_path = Path(directory)
        if not dir_path.exists():
            return
        
        current_time = datetime.now().timestamp()
        deleted_count = 0
        
        for file_path in dir_path.glob('*'):
            if file_path.is_file():
                file_age_days = (current_time - file_path.stat().st_mtime) / 86400
                if file_age_days > days:
                    file_path.unlink()
                    deleted_count += 1
        
        if deleted_count > 0:
            logger.info(f"清理了 {deleted_count} 个超过 {days} 天的旧文件")
            
    except Exception as e:
        logger.error(f"清理旧文件失败: {e}")


def parse_test_cases_from_markdown(markdown_text: str) -> List[Dict[str, str]]:
    """
    从Markdown表格格式解析测试用例
    
    Args:
        markdown_text: Markdown格式的测试用例文本
        
    Returns:
        测试用例列表，每个用例为一个字典
    """
    test_cases = []
    
    try:
        # 移除markdown代码块标记
        markdown_text = re.sub(r'```markdown\s*', '', markdown_text)
        markdown_text = re.sub(r'```\s*$', '', markdown_text)
        
        # 按行分割
        lines = markdown_text.strip().split('\n')
        
        # 找到表格头
        headers = []
        data_start_index = 0
        
        for i, line in enumerate(lines):
            if '|' in line and not line.strip().startswith('|--'):
                # 解析表头
                headers = [h.strip() for h in line.split('|') if h.strip()]
                data_start_index = i + 2  # 跳过表头和分隔符行
                break
        
        if not headers:
            logger.warning("未找到表格头部")
            return test_cases
        
        # 解析数据行
        for line in lines[data_start_index:]:
            if '|' in line and line.strip():
                # 清理行首尾的空格和竖线
                cells = [cell.strip() for cell in line.split('|') if cell.strip()]
                
                if len(cells) == len(headers):
                    test_case = dict(zip(headers, cells))
                    test_cases.append(test_case)
        
        logger.info(f"成功解析 {len(test_cases)} 条测试用例")
        
    except Exception as e:
        logger.error(f"解析Markdown测试用例失败: {e}")
    
    return test_cases


def export_to_excel(test_cases_markdown: str, output_path: str = None) -> BytesIO:
    """
    将测试用例导出为Excel格式
    
    Args:
        test_cases_markdown: Markdown格式的测试用例
        output_path: 输出文件路径（可选）
        
    Returns:
        BytesIO对象，包含Excel文件内容
    """
    try:
        from openpyxl import Workbook
        from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
        
        # 解析测试用例
        test_cases = parse_test_cases_from_markdown(test_cases_markdown)
        
        if not test_cases:
            logger.error("没有可导出的测试用例")
            return None
        
        # 创建工作簿
        wb = Workbook()
        ws = wb.active
        ws.title = "测试用例"
        
        # 获取表头
        headers = list(test_cases[0].keys())
        
        # 设置样式
        header_font = Font(name='微软雅黑', size=11, bold=True, color='FFFFFF')
        header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
        header_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
        
        cell_font = Font(name='微软雅黑', size=10)
        cell_alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
        
        border = Border(
            left=Side(style='thin', color='000000'),
            right=Side(style='thin', color='000000'),
            top=Side(style='thin', color='000000'),
            bottom=Side(style='thin', color='000000')
        )
        
        # 写入表头
        for col_idx, header in enumerate(headers, start=1):
            cell = ws.cell(row=1, column=col_idx)
            cell.value = header
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = header_alignment
            cell.border = border
        
        # 写入数据
        for row_idx, test_case in enumerate(test_cases, start=2):
            for col_idx, header in enumerate(headers, start=1):
                cell = ws.cell(row=row_idx, column=col_idx)
                cell_value = test_case.get(header, '')
                
                # 清理HTML标签（Excel中<br>转为换行符）
                cell_value = clean_html_tags(cell_value, for_markdown_table=False)
                
                cell.value = cell_value
                cell.font = cell_font
                cell.alignment = cell_alignment
                cell.border = border
        
        # 设置列宽
        column_widths = {
            '用例编号': 12,
            '用例标题': 25,
            '测试类型': 12,
            '优先级': 10,
            '前置条件': 30,
            '测试步骤': 40,
            '预期结果': 35
        }
        
        for col_idx, header in enumerate(headers, start=1):
            column_letter = ws.cell(row=1, column=col_idx).column_letter
            ws.column_dimensions[column_letter].width = column_widths.get(header, 20)
        
        # 设置行高
        ws.row_dimensions[1].height = 30
        for row_idx in range(2, len(test_cases) + 2):
            ws.row_dimensions[row_idx].height = 60
        
        # 保存到BytesIO
        excel_file = BytesIO()
        wb.save(excel_file)
        excel_file.seek(0)
        
        # 如果提供了输出路径，也保存到文件
        if output_path:
            output_file = Path(output_path)
            output_file.parent.mkdir(parents=True, exist_ok=True)
            wb.save(output_path)
            logger.info(f"Excel文件已保存到: {output_path}")
        
        logger.info(f"成功导出 {len(test_cases)} 条测试用例到Excel")
        return excel_file
        
    except ImportError:
        logger.error("请先安装openpyxl: pip install openpyxl")
        return None
    except Exception as e:
        logger.error(f"导出Excel失败: {e}")
        return None


def export_to_xmind(test_cases_markdown: str, output_path: str = None) -> BytesIO:
    """
    将测试用例导出为XMind格式
    
    Args:
        test_cases_markdown: Markdown格式的测试用例
        output_path: 输出文件路径（可选）
        
    Returns:
        BytesIO对象，包含XMind文件内容
    """
    try:
        import zipfile
        import xml.etree.ElementTree as ET
        from xml.dom import minidom
        
        # 解析测试用例
        test_cases = parse_test_cases_from_markdown(test_cases_markdown)
        
        if not test_cases:
            logger.error("没有可导出的测试用例")
            return None
        
        # 按测试类型分组
        test_type_groups = {}
        for test_case in test_cases:
            test_type = test_case.get('测试类型', '其他')
            if test_type not in test_type_groups:
                test_type_groups[test_type] = []
            test_type_groups[test_type].append(test_case)
        
        # 创建 XMind 8 格式的 content.xml
        xmap = ET.Element('xmap-content', xmlns='urn:xmind:xmap:xmlns:content:2.0', version='2.0')
        sheet = ET.SubElement(xmap, 'sheet', id='sheet1', timestamp=str(int(datetime.now().timestamp())))
        ET.SubElement(sheet, 'title').text = '测试用例'
        
        # 创建根主题
        root_topic = ET.SubElement(sheet, 'topic', id='root', timestamp=str(int(datetime.now().timestamp())))
        ET.SubElement(root_topic, 'title').text = '测试用例'
        root_children = ET.SubElement(root_topic, 'children')
        
        topic_id = 1
        
        # 构建思维导图结构
        # 创建根节点的topics容器（所有测试类型都在这里）
        root_topics_wrapper = ET.SubElement(root_children, 'topics', type='attached')
        
        for test_type, cases in test_type_groups.items():
            # 创建测试类型节点
            type_topic = ET.SubElement(root_topics_wrapper, 'topic', id=f'topic{topic_id}', 
                                      timestamp=str(int(datetime.now().timestamp())))
            topic_id += 1
            ET.SubElement(type_topic, 'title').text = f"{test_type} ({len(cases)}条)"
            type_children = ET.SubElement(type_topic, 'children')
            
            # 创建该测试类型下所有用例的topics容器
            case_topics_wrapper = ET.SubElement(type_children, 'topics', type='attached')
            
            for test_case in cases:
                # 创建测试用例节点
                case_title = test_case.get('用例标题', test_case.get('用例编号', '未命名'))
                case_title = clean_html_tags(case_title, for_markdown_table=False).replace('\n', ' ')  # 清理HTML标签
                case_topic = ET.SubElement(case_topics_wrapper, 'topic', id=f'topic{topic_id}',
                                          timestamp=str(int(datetime.now().timestamp())))
                topic_id += 1
                case_id = clean_html_tags(test_case.get('用例编号', ''), for_markdown_table=False).replace('\n', ' ')
                ET.SubElement(case_topic, 'title').text = f"{case_id} - {case_title}"
                case_children = ET.SubElement(case_topic, 'children')
                
                # 添加详细信息节点
                details_topics = ET.SubElement(case_children, 'topics', type='attached')
                
                # 添加优先级
                if '优先级' in test_case:
                    pri_topic = ET.SubElement(details_topics, 'topic', id=f'topic{topic_id}',
                                            timestamp=str(int(datetime.now().timestamp())))
                    topic_id += 1
                    priority_text = clean_html_tags(test_case['优先级'], for_markdown_table=False).replace('\n', ' ')
                    ET.SubElement(pri_topic, 'title').text = f"优先级: {priority_text}"
                
                # 添加前置条件
                if '前置条件' in test_case:
                    pre_topic = ET.SubElement(details_topics, 'topic', id=f'topic{topic_id}',
                                            timestamp=str(int(datetime.now().timestamp())))
                    topic_id += 1
                    precondition_text = clean_html_tags(test_case['前置条件'], for_markdown_table=False).replace('\n', ' ')
                    ET.SubElement(pre_topic, 'title').text = f"前置条件: {precondition_text}"
                
                # 添加测试步骤
                if '测试步骤' in test_case:
                    steps_topic = ET.SubElement(details_topics, 'topic', id=f'topic{topic_id}',
                                              timestamp=str(int(datetime.now().timestamp())))
                    topic_id += 1
                    ET.SubElement(steps_topic, 'title').text = "测试步骤"
                    
                    # 清理HTML标签并分割测试步骤（XMind中<br>转为换行符）
                    steps_text = clean_html_tags(test_case['测试步骤'], for_markdown_table=False)
                    
                    # 分割测试步骤（支持多种分隔符）
                    steps = [s.strip() for s in steps_text.split('；') if s.strip()]
                    if not steps:
                        steps = [s.strip() for s in steps_text.split(';') if s.strip()]
                    if not steps:
                        # 如果没有分号，尝试按换行符分割
                        steps = [s.strip() for s in steps_text.split('\n') if s.strip()]
                    
                    # 只有当有多个步骤时才创建子节点
                    if len(steps) > 0:
                        steps_children = ET.SubElement(steps_topic, 'children')
                        steps_topics = ET.SubElement(steps_children, 'topics', type='attached')
                        
                        for step in steps:
                            step_topic = ET.SubElement(steps_topics, 'topic', id=f'topic{topic_id}',
                                                      timestamp=str(int(datetime.now().timestamp())))
                            topic_id += 1
                            ET.SubElement(step_topic, 'title').text = step
                
                # 添加预期结果
                if '预期结果' in test_case:
                    result_topic = ET.SubElement(details_topics, 'topic', id=f'topic{topic_id}',
                                                timestamp=str(int(datetime.now().timestamp())))
                    topic_id += 1
                    result_text = clean_html_tags(test_case['预期结果'], for_markdown_table=False).replace('\n', ' ')
                    ET.SubElement(result_topic, 'title').text = f"预期结果: {result_text}"
        
        # 格式化 XML
        xml_str = minidom.parseString(ET.tostring(xmap)).toprettyxml(indent="  ", encoding='UTF-8')
        
        # 创建 XMind 文件 (ZIP格式)
        if output_path is None:
            output_path = f"temp_xmind_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xmind"
        
        output_file = Path(output_path)
        output_file.parent.mkdir(parents=True, exist_ok=True)
        
        with zipfile.ZipFile(str(output_file), 'w', zipfile.ZIP_DEFLATED) as zf:
            # 添加 content.xml
            zf.writestr('content.xml', xml_str)
            
            # 添加 META-INF/manifest.xml
            manifest = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<manifest xmlns="urn:xmind:xmap:xmlns:manifest:1.0">
    <file-entry full-path="content.xml" media-type="text/xml"/>
</manifest>'''
            zf.writestr('META-INF/manifest.xml', manifest.encode('utf-8'))
            
            # 添加 meta.xml
            meta = f'''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<meta xmlns="urn:xmind:xmap:xmlns:meta:2.0" version="2.0">
    <Author>
        <Name>AI测试用例生成智能体</Name>
    </Author>
    <Create>
        <Time>{datetime.now().isoformat()}</Time>
    </Create>
</meta>'''
            zf.writestr('meta.xml', meta.encode('utf-8'))
        
        # 读取文件到BytesIO
        with open(output_file, 'rb') as f:
            xmind_bytes = BytesIO(f.read())
        
        xmind_bytes.seek(0)
        
        logger.info(f"成功导出 {len(test_cases)} 条测试用例到XMind")
        return xmind_bytes
        
    except Exception as e:
        logger.error(f"导出XMind失败: {e}")
        import traceback
        logger.error(traceback.format_exc())
        return None

